package com.cccyy.encry;

import com.alibaba.fastjson.JSON;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException;
import org.apache.commons.io.FileUtils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;

public class EntryUtils {

    /**
     * @param input     明文
     * @param algorithm 算法  MD5 | sha-1 SHA-256 |
     * @return 密文  Base64 & Hex
     */
    private static String toHexOrBase64(String input, String algorithm) throws NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance(algorithm);
        byte[] digest1 = digest.digest(input.getBytes(StandardCharsets.UTF_8));
        String base64 = Base64.encode(digest1);
        StringBuffer haxValue = new StringBuffer();
        for (byte b : digest1) {
            //0xff是16进制数，这个刚好8位都是1的二进制数，而且转成int类型的时候，高位会补0
            int val = ((int) b) & 0xff;//只取得低八位
            //在&正数byte值的话，对数值不会有改变 在&负数数byte值的话，对数值前面补位的1会变成0，
            if (val < 16) {
                haxValue.append("0");//位数不够，高位补0
            }
            haxValue.append(Integer.toHexString(val));
        }
        HashMap<String, String> DigestMap = new HashMap<>();
        DigestMap.put("Base64", base64);
        DigestMap.put("Hex", String.valueOf(haxValue));
        return JSON.toJSONString(DigestMap);
    }

    /**
     * @param text           明文/base64密文
     * @param key            密钥
     * @param transformation 转换方式
     * @param mode           加密/解密
     */
    private static String extracted(String text, String key, String transformation, boolean mode) throws NoSuchAlgorithmException,
            NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        Cipher cipher = Cipher.getInstance(transformation);
        //    key          与给定的密钥内容相关联的密钥算法的名称
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), transformation);
        //Cipher 的操作模式,加密模式：ENCRYPT_MODE、 解密模式：DECRYPT_MODE、包装模式：WRAP_MODE 或 解包装：UNWRAP_MODE）
        cipher.init(mode ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secretKeySpec);
        byte[] bytes = cipher.doFinal(mode ? text.getBytes(StandardCharsets.UTF_8) : Base64.decode(text));
        return mode ? Base64.encode(bytes) : new String(bytes);
    }


    public static void main(String[] args) throws Exception {
        String text = "你好世界！！";

        String key = "12345678";//des必须8字节
        // 算法/模式/填充  默认 DES/ECB/PKCS5Padding
        String transformation = "DES";

        String key1 = "1234567812345678";//aes必须16字节
        String transformation1 = "AES";

        String key2 = "123456781234567812345678";//TripleDES使用24字节的key
        String transformation2 = "TripleDes";

        String extracted = extracted(text, key, transformation, true);
        System.out.println("DES加密：" + extracted);
        String extracted1 = extracted(extracted, key, transformation, false);
        System.out.println("解密：" + extracted1);


        String extracted2 = extracted(text, key1, transformation1, true);
        System.out.println("AES加密：" + extracted2);
        String extracted3 = extracted(extracted2, key1, transformation1, false);
        System.out.println("解密：" + extracted3);

        String extracted4 = extracted(text, key2, transformation2, true);
        System.out.println("Triple Des加密：" + extracted4);
        String extracted5 = extracted(extracted, key2, transformation2, false);
        System.out.println("解密：" + extracted5);


        String algorithm = "RSA";

        PublicKey publicKey = getPublicKey(algorithm, "rsaKey/publicKey2.txt");
        PrivateKey privateKey = getPrivateKey(algorithm, "rsaKey/privateKey2.txt");

        String s = RSAEncrypt(text, algorithm, publicKey);
        String s1 = RSADecrypt(s, algorithm, privateKey);

        System.out.println(s);
        System.out.println(s1);

        //generateKeyFile("DSA","D:\\privateKey2.txt","D:\\publicKey2.txt");
    }


    /**
     * 获取公钥,key
     *
     * @param algorithm  算法
     * @param publicPath 密匙文件路径
     * @return
     */
    private static PublicKey getPublicKey(String algorithm, String publicPath) throws IOException,
            NoSuchAlgorithmException, Base64DecodingException, InvalidKeySpecException {
        String publicEncodeString = FileUtils.readFileToString(new File(publicPath), String.valueOf(StandardCharsets.UTF_8));
        //返回转换指定算法的 public/private 关键字的 KeyFactory 对象。
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        //此类表示根据 ASN.1 类型 SubjectPublicKeyInfo 进行编码的公用密钥的 ASN.1 编码
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decode(publicEncodeString));
        return keyFactory.generatePublic(x509EncodedKeySpec);
    }

    /**
     * 获取私钥，key
     *
     * @param algorithm   算法
     * @param privatePath 密匙文件路径
     * @return
     */
    private static PrivateKey getPrivateKey(String algorithm, String privatePath) throws IOException,
            NoSuchAlgorithmException, Base64DecodingException, InvalidKeySpecException {
        String privateEncodeString = FileUtils.readFileToString(new File(privatePath), String.valueOf(StandardCharsets.UTF_8));
        //返回转换指定算法的 public/private 关键字的 KeyFactory 对象。
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        //创建私钥key的规则  此类表示按照 ASN.1 类型 PrivateKeyInfo 进行编码的专用密钥的 ASN.1 编码
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decode(privateEncodeString));
        //私钥对象
        return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
    }

    /**
     * 加密
     *
     * @param text      明文
     * @param algorithm 算法
     * @param key       私钥/密钥
     * @return 密文
     */
    private static String RSAEncrypt(String text, String algorithm, Key key) throws NoSuchAlgorithmException,
            NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException {
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] bytes = cipher.doFinal(text.getBytes(StandardCharsets.UTF_8));
        return Base64.encode(bytes);
    }

    /**
     * 解密
     *
     * @param extracted 密文
     * @param algorithm 算法
     * @param key       密钥/私钥
     * @return String 明文
     */
    private static String RSADecrypt(String extracted, String algorithm, Key key) throws NoSuchAlgorithmException,
            NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, Base64DecodingException, NoSuchProviderException {
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] bytes1 = cipher.doFinal(Base64.decode(extracted));
        return new String(bytes1);
    }

    /**
     * 生成公钥和私钥文件
     *
     * @param algorithm   算法
     * @param privatePath 私钥路径
     * @param publicPath  公钥路径
     */
    private static void generateKeyFile(String algorithm, String privatePath, String publicPath) throws NoSuchAlgorithmException, IOException {
        //返回生成指定算法的 public/private 密钥对的 KeyPairGenerator 对象
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        //生成一个密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        //私钥
        PrivateKey privateKey = keyPair.getPrivate();
        //公钥
        PublicKey publicKey = keyPair.getPublic();

        byte[] privateKeyEncoded = privateKey.getEncoded();
        byte[] publicKeyEncoded = publicKey.getEncoded();

        String privateEncodeString = Base64.encode(privateKeyEncoded);
        String publicEncodeString = Base64.encode(publicKeyEncoded);
        //需导入commons-io
        FileUtils.writeStringToFile(new File(privatePath), privateEncodeString, String.valueOf(StandardCharsets.UTF_8));
        FileUtils.writeStringToFile(new File(publicPath), publicEncodeString, String.valueOf(StandardCharsets.UTF_8));
    }

}
